/**
*
*/
package edu.gatech.i3l.fhir.security;
import java.nio.charset.Charset;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.StringUtils;
import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.apache.oltu.oauth2.common.message.types.ParameterStyle;
import org.apache.oltu.oauth2.rs.request.OAuthAccessResourceRequest;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
//import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.RequestDetails;
/**
* @author MC142
*
*/
public class Authorization {
private String url;
private String clientId;
private String clientSecret;
private String token;
private String userId;
private String password;
private String token_type;
private int myTimeSkewAllowance = 300;
private boolean active = false;
private boolean expired = true;
private boolean is_admin = false;
private Set<String> scopeSet;
public Authorization(String url) {
this.url = url;
this.clientId = "client";
this.clientSecret = "secret";
}
public Authorization(String url, String clientId, String clientSecret) {
this.url = url;
this.clientId = clientId;
this.clientSecret = clientSecret;
}
public String getClientId() {
return clientId;
}
public String getUserId() {
return userId;
}
private HttpHeaders createHeaders () {
HttpHeaders httpHeaders = new HttpHeaders();
String auth = clientId+":"+clientSecret;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
httpHeaders.set("Authorization", authHeader);
httpHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
return httpHeaders;
// return new HttpHeaders() {
// {
// String auth = clientId+":"+clientSecret;
// byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));
// String authHeader = "Basic " + new String(encodedAuth);
// set("Authorization", authHeader);
// setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
// }
// };
}
public String introspectToken(HttpServletRequest request) {
OAuthAccessResourceRequest oauthRequest;
try {
oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.HEADER);
// Get the access token
String accessToken = oauthRequest.getAccessToken();
if (introspectToken(accessToken) == false) {
return "Invalid Access Token";
}
} catch (OAuthSystemException | OAuthProblemException e) {
e.printStackTrace();
return "Invalid Auth Request";
}
return "";
}
public boolean introspectToken(String token) {
// Sanity Check.
if (token == null || token.isEmpty()) {
return false;
}
// Save the token for a future use.
this.token = token;
// Introspect the token
RestTemplate restTemplate = new RestTemplate();
HttpEntity<String> reqAuth = new HttpEntity<String>(createHeaders());
ResponseEntity<String> response;
String introspectTokenUrl = url+"?token="+this.token;
response = restTemplate.exchange(introspectTokenUrl, HttpMethod.POST, reqAuth, String.class);
HttpStatus statusCode = response.getStatusCode();
if (statusCode.is2xxSuccessful() == false) {
return false;
}
System.out.println("IntrospectToken: "+response.getBody());
// First check the token status. Turn the body into JSON.
JSONObject jsonObject = new JSONObject(response.getBody());
if (jsonObject.getBoolean("active") != true) {
// This is not active token.
active = false;
return false;
}
active = true;
// Get the expiration time.
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
Date expDate;
try {
expDate = df.parse(jsonObject.getString("exp"));
} catch (JSONException | ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
expired = true;
return false;
}
Date minAllowableExpirationTime = new Date(System.currentTimeMillis()-(myTimeSkewAllowance * 1000L));
if (expDate != null && expDate.before(minAllowableExpirationTime)) {
// expired.
expired = true;
return false;
}
// Store the received information such as scope, user_id, client_id, etc...
userId = jsonObject.getString("sub");
clientId = jsonObject.getString("client_id");
token_type = jsonObject.getString("token_type");
String[] scopeValues = jsonObject.getString("scope")
.trim().replaceAll("\\+", " ")
.split(" ");
scopeSet = new HashSet<String>(Arrays.asList(scopeValues));
if (scopeSet.isEmpty()) return false;
if (scopeSet.contains("user/*.*")) {
is_admin = true;
}
return true;
}
public boolean checkBearer() {
if (token_type != null && token_type.equalsIgnoreCase("Bearer")) {
return true;
} else {
return false;
}
}
public boolean assertScope(String myScope) {
for (String scope : scopeSet) {
if (scope.equalsIgnoreCase(myScope))
return true;
}
return false;
}
public boolean allowRequest(RequestDetails theRequestDetails) {
if (checkBearer() == false) {
return false;
}
if (is_admin) {
return true;
}
// TODO: Check the request detail and compare with scope. If out of scope, then
// return false.
// We need to have user or patient level permission checking. For now, user/ and patient/ scope has
// all patients permission. We need to revisit this.
//
String resourceName = theRequestDetails.getResourceName();
RestOperationTypeEnum resourceOperationType = theRequestDetails.getRestOperationType();
for (String scope : scopeSet) {
String[] scopeDetail = scope.split("/");
if (resourceOperationType == RestOperationTypeEnum.READ ||
resourceOperationType == RestOperationTypeEnum.VREAD ||
resourceOperationType == RestOperationTypeEnum.SEARCH_TYPE) {
if ((scopeDetail[1].equalsIgnoreCase("*.read") || scopeDetail[1].equalsIgnoreCase("*.*"))) {
return true;
} else {
String[] scopeResource = scopeDetail[1].split(".");
if (scopeResource[0].equalsIgnoreCase(resourceName) &&
(scopeResource[1].equalsIgnoreCase("read") || scopeResource[1].equalsIgnoreCase("*"))) {
return true;
}
}
} else {
// This is CREATE, UPDATE, DELETE... write permission is required.
if ((scopeDetail[1].equalsIgnoreCase("*.write") || scopeDetail[1].equalsIgnoreCase("*.*"))) {
return true;
} else {
String[] scopeResource = scopeDetail[1].split(".");
if (scopeResource[0].equalsIgnoreCase(resourceName) &&
(scopeResource[1].equalsIgnoreCase("write") || scopeResource[1].equalsIgnoreCase("*"))) {
return true;
}
}
}
}
System.out.println(resourceName+" "+resourceOperationType.name()+" request failed to get Authorization.");
return false;
}
// Belows are for out-of-band authorization to support Smart on FHIR internal communications.
// This is not Smart on FHIR standard. This is to support Smart on FHIR's authorization server.
public boolean asBasicAuth(HttpServletRequest request) {
String authString = request.getHeader("Authorization");
if (authString == null) return false;
System.out.println("asBasicAuth auth header:"+authString);
// String[] credential = OAuthUtils.decodeClientAuthenticationHeader(authString);
if (authString.regionMatches(0, "Basic", 0, 5) == false) return false; // Not a basic Auth
String credentialString = StringUtils.newStringUtf8(Base64.decodeBase64(authString.substring(6)));
if (credentialString == null) return false;
String[] credential = credentialString.trim().split(":");
if (credential.length != 2) return false;
userId = credential[0];
password = credential[1];
System.out.println("asBasicAuth:"+userId+":"+password);
if (userId.equalsIgnoreCase(clientId) && password.equalsIgnoreCase(clientSecret))
return true;
else
return false;
}
public boolean asBearerAuth(HttpServletRequest request) {
OAuthAccessResourceRequest oauthRequest;
try {
oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.HEADER);
// Get the access token
String accessToken = oauthRequest.getAccessToken();
return introspectToken(accessToken);
} catch (OAuthSystemException | OAuthProblemException e) {
e.printStackTrace();
return false;
}
}
}